/* === DECORAZIONE CMS PRO v2 - TOTALMENTE AUTOADMINISTRABLE === */ (() => { const DB_NAME = "decorazione_media_db"; const STORE = "media"; let db; function openDB(){ return new Promise((res, rej)=>{ const req = indexedDB.open(DB_NAME, 1); req.onupgradeneeded = e => { const d = e.target.result; if(!d.objectStoreNames.contains(STORE)) d.createObjectStore(STORE, {keyPath:"id"}); }; req.onsuccess = e => { db = e.target.result; res(db); }; req.onerror = rej; }); } function saveMediaBlob(id, blob, meta={}){ return new Promise((res, rej)=>{ const tx = db.transaction(STORE,"readwrite"); tx.objectStore(STORE).put({id, blob, ...meta, date: Date.now()}); tx.oncomplete = ()=>res(id); tx.onerror = rej; }); } function getMediaBlob(id){ return new Promise((res, rej)=>{ const tx = db.transaction(STORE,"readonly"); const req = tx.objectStore(STORE).get(id); req.onsuccess = ()=>res(req.result); req.onerror = rej; }); } function getAllMedia(){ return new Promise((res, rej)=>{ const tx = db.transaction(STORE,"readonly"); const req = tx.objectStore(STORE).getAll(); req.onsuccess = ()=>res(req.result); req.onerror = rej; }); } const CMS_KEY = "dz_pro_cms_v2"; let cms = JSON.parse(localStorage.getItem(CMS_KEY)||"{}"); if(!cms.pages) cms.pages = []; if(!cms.banners) cms.banners = []; if(!cms.mediaLib) cms.mediaLib = []; function persist(){ localStorage.setItem(CMS_KEY, JSON.stringify(cms)); } function injectAdminStyles(){ if(document.getElementById("dz-pro-style")) return; const s = document.createElement("style"); s.id="dz-pro-style"; s.textContent=` #dz-admin-dock{position:fixed;left:12px;right:12px;bottom:12px;z-index:999999;background:#0f172a;color:#fff;border-radius:16px;padding:10px 12px;display:flex;gap:8px;flex-wrap:wrap;align-items:center;box-shadow:0 20px 40px rgba(0,0,0,.4);font-family:system-ui} #dz-admin-dock button{border:0;border-radius:10px;padding:8px 12px;font:700 12px system-ui;cursor:pointer} .dz-p{ background:#fff;color:#0f172a; border:1px solid #e2e8f0} .dz-s{ background:#1e293b;color:#cbd5e1} .dz-accent{ background:#f59e0b;color:#000} .dz-editable{outline:2px dashed #f59e0b !important; outline-offset:2px; position:relative} .dz-hover-actions{position:absolute;top:-28px;left:0;background:#0f172a;color:#fff;border-radius:8px;display:flex;gap:4px;padding:4px;z-index:9999} .dz-hover-actions button{padding:4px 6px;font:700 10px system-ui;background:#334155;color:#fff;border:0;border-radius:6px} .dz-modal-pro{position:fixed;inset:0;background:rgba(2,6,23,.7);backdrop-filter:blur(8px);z-index:1000000;display:flex;align-items:center;justify-content:center;padding:20px} .dz-modal-inner{width:min(1100px,95vw);max-height:90vh;overflow:auto;background:#fff;border-radius:20px;padding:22px;box-shadow:0 30px 80px rgba(0,0,0,.5)} .dz-dropzone{border:2px dashed #cbd5e1;border-radius:14px;padding:20px;text-align:center;background:#f8fafc} .dz-dropzone.dragover{border-color:#f59e0b;background:#fffbeb} .dz-media-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:12px} .dz-media-card{border:1px solid #e2e8f0;border-radius:12px;overflow:hidden;background:#fff} .dz-media-card img,.dz-media-card video{width:100%;height:120px;object-fit:cover} #dz-edit-bar{position:fixed;top:12px;left:50%;transform:translateX(-50%);z-index:999999;background:#fff;border:1px solid #e2e8f0;border-radius:999px;padding:6px 10px;display:flex;gap:6px;box-shadow:0 10px 30px rgba(0,0,0,.15)} `; document.head.appendChild(s); } function makeEditable(){ // All text elements editable on click when edit mode on if(!cms.editMode) return; document.querySelectorAll("h1,h2,h3,h4,p,span,a,button,li").forEach(el=>{ if(el.closest("#dz-admin-dock")||el.closest(".dz-modal-pro")||el.closest("#dz-edit-bar")) return; el.addEventListener("mouseenter", ()=>{ if(!cms.editMode) return; el.classList.add("dz-editable"); if(el.querySelector(".dz-hover-actions")) return; const bar = document.createElement("div"); bar.className="dz-hover-actions"; bar.innerHTML=``; bar.onclick = (ev)=>{ ev.stopPropagation(); const act = ev.target.dataset.act; if(act==="edit-text"){ const nv = prompt("Nuevo texto:", el.innerText); if(nv!==null){ el.innerText = nv; autoSaveDOM(); } } else if(act==="dup"){ const clone = el.cloneNode(true); clone.classList.remove("dz-editable"); const oldActions = clone.querySelector(".dz-hover-actions"); if(oldActions) oldActions.remove(); el.parentNode.insertBefore(clone, el.nextSibling); autoSaveDOM(); } else if(act==="del"){ if(confirm("¿Eliminar este elemento?")){ el.remove(); autoSaveDOM(); } } else if(act==="replace-img"){ openMediaLibrary((url)=>{ if(el.tagName==="IMG"){ el.src=url; } else { el.style.backgroundImage=`url(${url})`; } autoSaveDOM(); }); } }; el.appendChild(bar); }); el.addEventListener("mouseleave", ()=>{ setTimeout(()=>{ if(!el.matches(":hover")){ el.classList.remove("dz-editable"); const b = el.querySelector(".dz-hover-actions"); if(b) b.remove(); } },200); }); }); } function autoSaveDOM(){ // Save snapshot of body HTML structure (only main content) cms.domSnapshot = document.documentElement.outerHTML; // we store only up to 5MB in localStorage, so we store diff? We'll store in IndexedDB if large persist(); console.log("Decorazione: guardado automático"); } function buildDock(){ if(document.getElementById("dz-admin-dock")) return; const dock = document.createElement("div"); dock.id="dz-admin-dock"; dock.innerHTML=` DECORAZIONE CMS PRO Imágenes/videos ilimitados • IndexedDB • Duplicación total `; document.body.appendChild(dock); dock.querySelector("#dz-toggle-edit").onclick=()=>{ cms.editMode=!cms.editMode; persist(); location.reload(); }; dock.querySelector("#dz-media-lib").onclick=()=>openMediaLibrary(); dock.querySelector("#dz-banner-mgr").onclick=()=>openBannerManager(); dock.querySelector("#dz-page-mgr").onclick=()=>openPageManager(); dock.querySelector("#dz-section-dup").onclick=()=>enableSectionDuplicator(); dock.querySelector("#dz-export-full").onclick=()=>exportAll(); dock.querySelector("#dz-import-full").onclick=()=>importAll(); dock.querySelector("#dz-clear").onclick=()=>{ if(confirm("¿Restaurar diseño original? Se borrarán cambios locales (no afecta archivos exportados)")){ localStorage.removeItem(CMS_KEY); indexedDB.deleteDatabase(DB_NAME); location.reload(); } }; } function openMediaLibrary(onSelect){ const wrap = document.createElement("div"); wrap.className="dz-modal-pro"; wrap.innerHTML=`

Biblioteca de medios ILIMITADA

Arrastra aquí imágenes y videos sin límite
Soporta JPG, PNG, WEBP, MP4, WEBM, GIF. Almacenamiento en IndexedDB (cientos de MB / GB según navegador)

También puedes pegar URL externa:

Cómo usar: Click en una imagen para copiar URL / reemplazar. Botón Usar cuando estás en modo reemplazo. Puedes subir 1000+ fotos, el límite es el disco del navegador, no el CMS.
`; document.body.appendChild(wrap); wrap.querySelector("#dz-close-modal").onclick=()=>wrap.remove(); wrap.onclick=(e)=>{ if(e.target===wrap) wrap.remove(); }; const grid = wrap.querySelector("#dz-grid"); const fileInput = wrap.querySelector("#dz-file-input"); const drop = wrap.querySelector("#dz-drop"); async function refreshGrid(){ const all = await getAllMedia(); grid.innerHTML = all.map(m=>{ const url = URL.createObjectURL(m.blob); const isVideo = m.blob.type.startsWith("video"); return `
${isVideo?``:``}
${m.name||m.id}
${(m.blob.size/1024/1024).toFixed(2)} MB
`; }).join("") + (all.length===0?'

Aún no hay archivos. ¡Sube tus fotos de cortinas!

':''); grid.querySelectorAll("[data-use]").forEach(b=>{ b.onclick=async()=>{ const rec = await getMediaBlob(b.dataset.use); const url = URL.createObjectURL(rec.blob); if(onSelect){ onSelect(url, rec); wrap.remove(); } else { navigator.clipboard.writeText(url); alert("URL temporal creada (válida en esta sesión). Para uso permanente, exporta."); } }; }); grid.querySelectorAll("[data-del]").forEach(b=>{ b.onclick=()=>{ if(!confirm("¿Borrar?")) return; const tx = db.transaction(STORE,"readwrite"); tx.objectStore(STORE).delete(b.dataset.del); tx.oncomplete=refreshGrid; }; }); } async function handleFiles(files){ for(const f of files){ const id = "media-"+Date.now()+"-"+Math.random().toString(36).slice(2); await saveMediaBlob(id, f, {name:f.name, type:f.type}); } await refreshGrid(); } fileInput.onchange = ()=> handleFiles(fileInput.files); drop.ondragover = e=>{ e.preventDefault(); drop.classList.add("dragover"); }; drop.ondragleave = ()=> drop.classList.remove("dragover"); drop.ondrop = e=>{ e.preventDefault(); drop.classList.remove("dragover"); handleFiles(e.dataTransfer.files); }; wrap.querySelector("#dz-add-url").onclick=()=>{ const u = wrap.querySelector("#dz-url-input").value.trim(); if(!u) return; // fetch and store as blob if possible fetch(u).then(r=>r.blob()).then(async blob=>{ const id = "media-url-"+Date.now(); await saveMediaBlob(id, blob, {name:u.split("/").pop(), type:blob.type, url:u}); refreshGrid(); }).catch(()=>alert("No se pudo descargar. La URL queda referenciada igual. Úsala directo.")); }; refreshGrid(); } function openBannerManager(){ const wrap = document.createElement("div"); wrap.className="dz-modal-pro"; wrap.innerHTML=`

Gestor de Banners

Crea, duplica y edita banners sin límite. Aparecerán en la web automáticamente.

`; document.body.appendChild(wrap); const list = wrap.querySelector("#dz-banner-list"); function render(){ list.innerHTML = (cms.banners||[]).map((b,i)=>`
${b.title||'Banner '+(i+1)}
${b.text||''}
`).join("") || "

No hay banners

"; } wrap.querySelector("#dz-new-banner").onclick=()=>{ const title = prompt("Título banner:"); if(title===null) return; cms.banners.push({id:"bn-"+Date.now(), title, text: prompt("Texto:")||"", image: prompt("URL imagen (o deja vacío y luego usa biblioteca):")||"", link: prompt("Link destino:")||"#"}); persist(); render(); injectBannersToPage(); }; wrap.querySelector("#dz-dup-banner").onclick=()=>{ if(!cms.banners.length) return alert("No hay banner para duplicar"); const last = JSON.parse(JSON.stringify(cms.banners[cms.banners.length-1])); last.id="bn-"+Date.now(); last.title+=" - Copia"; cms.banners.push(last); persist(); render(); injectBannersToPage(); }; list.onclick = e=>{ const idx = e.target.dataset.edit; if(idx!==undefined){ const b = cms.banners[idx]; b.title = prompt("Título:", b.title)||b.title; b.text = prompt("Texto:", b.text)||b.text; b.image = prompt("Imagen URL:", b.image)||b.image; b.link = prompt("Link:", b.link)||b.link; persist(); render(); injectBannersToPage(); } const del = e.target.dataset.del; if(del!==undefined){ if(confirm("¿Borrar banner?")){ cms.banners.splice(del,1); persist(); render(); injectBannersToPage(); } } }; render(); } function injectBannersToPage(){ let container = document.getElementById("dz-dynamic-banners"); if(!container){ container = document.createElement("div"); container.id="dz-dynamic-banners"; container.style.cssText="max-width:1280px;margin:20px auto;padding:0 16px;display:grid;gap:16px"; document.body.insertBefore(container, document.body.firstChild.nextSibling); } container.innerHTML = (cms.banners||[]).map(b=>`

${b.title||''}

${b.text||''}

`).join(""); } function openPageManager(){ const wrap = document.createElement("div"); wrap.className="dz-modal-pro"; const pages = cms.pages; wrap.innerHTML=`

Gestor de Páginas / Categorías / Productos

Duplica cualquier página, sección, producto, foto o texto. Estructura infinita.

`; document.body.appendChild(wrap); const list = wrap.querySelector("#dz-page-list"); function render(){ list.innerHTML = pages.map((p,i)=>`
${p.title}
Slug: /${p.slug} • Bloques: ${p.blocks?.length||0}
`).join("") || "

No hay páginas custom. Crea la primera.

"; } wrap.querySelector("#dz-new-page").onclick=()=>{ const title = prompt("Nombre de la nueva página / categoría:"); if(!title) return; const slug = title.toLowerCase().replace(/\s+/g,'-').replace(/[^a-z0-9-]/g,''); pages.push({id:"page-"+Date.now(), title, slug, blocks:[]}); persist(); render(); alert("Página creada: /"+slug+" - Ahora puedes duplicar contenido dentro de ella con el modo edición."); }; wrap.querySelector("#dz-dup-dom").onclick=()=>{ wrap.remove(); enableSectionDuplicator(); }; list.onclick=e=>{ const dup = e.target.dataset.dup; if(dup!==undefined){ const copy = JSON.parse(JSON.stringify(pages[dup])); copy.id="page-"+Date.now(); copy.title+=" - Copia"; copy.slug+="-copia"; pages.splice(parseInt(dup)+1,0,copy); persist(); render(); } const del = e.target.dataset.del; if(del!==undefined){ if(confirm("¿Borrar página?")){ pages.splice(del,1); persist(); render(); } } }; render(); } function enableSectionDuplicator(){ alert("Modo duplicación: pasa el mouse sobre cualquier sección/card/imagen del sitio y verás el botón Duplicar. Activa también el Modo edición en la barra inferior para editar textos al instante."); cms.editMode=true; persist(); makeEditable(); } function exportAll(){ const data = {cms, localStorageSnapshot: {...localStorage}}; const blob = new Blob([JSON.stringify(data,null,2)], {type:"application/json"}); const a = document.createElement("a"); a.href=URL.createObjectURL(blob); a.download="decorazione-backup-completo-"+Date.now()+".json"; a.click(); } function importAll(){ const input = document.createElement("input"); input.type="file"; input.accept=".json"; input.onchange=async()=>{ const file = input.files[0]; if(!file) return; const text = await file.text(); try{ const data = JSON.parse(text); if(data.cms){ localStorage.setItem(CMS_KEY, JSON.stringify(data.cms)); } alert("Importado correctamente. Recargando..."); location.reload(); }catch(e){ alert("Archivo inválido: "+e.message); } }; input.click(); } async function initPro(){ await openDB(); injectAdminStyles(); buildDock(); injectBannersToPage(); if(cms.editMode) { const bar = document.createElement("div"); bar.id="dz-edit-bar"; bar.innerHTML=`MODO EDICIÓN ACTIVOClick en cualquier texto para editar • Hover para duplicar/borrar`; document.body.appendChild(bar); makeEditable(); } console.log("Decorazione CMS PRO v2 listo - Media ilimitada con IndexedDB"); } if(document.readyState==="loading") document.addEventListener("DOMContentLoaded", initPro); else initPro(); })();